/*
 *  ETA/OS - Python VM
 *  Copyright (C) 2017   Michel Megens <dev@bietje.net>
 *  Copyright (C) 2017   Dean Hall
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#undef __FILE_ID__
#define __FILE_ID__ 0x0F

/**
 * \file
 * \brief Object Type
 *
 * Object type operations.
 */

#include <etaos/python.h>

PmReturn_t
obj_loadFromImg(PmMemSpace_t memspace, uint8_t const **paddr, pPmObj_t * r_pobj)
{
	PmReturn_t retval = PM_RET_OK;
	PmObj_t obj;

	/* Get the object descriptor */
	obj.od = (PmObjDesc_t) 0x0000;
	OBJ_SET_TYPE(&obj, mem_getByte(memspace, paddr));

	switch (OBJ_GET_TYPE(&obj)) {
	case OBJ_TYPE_NON:
		/* If it's the None object, return global None */
		*r_pobj = PM_NONE;
		break;

	case OBJ_TYPE_INT:
		/* Read an integer and create an integer object with the value */
		retval = int_new(mem_getInt(memspace, paddr), r_pobj);
		break;

#ifdef HAVE_FLOAT
	case OBJ_TYPE_FLT:
		/* Read a float and create an float object with the value */
		retval = float_new(mem_getFloat(memspace, paddr), r_pobj);
		break;
#endif				/* HAVE_FLOAT */

	case OBJ_TYPE_STR:
		retval = string_loadFromImg(memspace, paddr, r_pobj);
		break;

	case OBJ_TYPE_TUP:
		retval = tuple_loadFromImg(memspace, paddr, r_pobj);
		break;

	case OBJ_TYPE_NIM:
		/* If it's a native code img, load into a code obj */
		retval = no_loadFromImg(memspace, paddr, r_pobj);
		break;

	case OBJ_TYPE_CIM:
		/* If it's a code img, load into a code obj */
		retval = co_loadFromImg(memspace, paddr, r_pobj);
		break;

	default:
		/* All other types should not be in an img obj */
		PM_RAISE(retval, PM_RET_EX_SYS);
		break;
	}
	return retval;
}

PmReturn_t obj_loadFromImgObj(pPmObj_t pimg, pPmObj_t * r_pobj)
{
	uint8_t const *imgaddr;
	PmReturn_t retval;

	C_ASSERT(OBJ_GET_TYPE(pimg) == OBJ_TYPE_CIO);
	imgaddr = (uint8_t const *)&(((pPmCodeImgObj_t) pimg)->val);

	retval = obj_loadFromImg(MEMSPACE_RAM, &imgaddr, r_pobj);
	C_ASSERT(OBJ_GET_TYPE(*r_pobj) == OBJ_TYPE_COB);

	/* All COs must reference the top of the code img obj
	 * so the image is marked and prevented from being reclaimed */
	co_rSetCodeImgAddr((pPmCo_t) * r_pobj, (uint8_t const *)pimg);

	return retval;
}

/* Returns true if the obj is false */
int8_t obj_isFalse(pPmObj_t pobj)
{
	C_ASSERT(pobj != C_NULL);

	switch (OBJ_GET_TYPE(pobj)) {
	case OBJ_TYPE_NON:
		/* None evaluates to false, so return true */
		return C_TRUE;

	case OBJ_TYPE_INT:
		/* Only the integer zero is false */
		return ((pPmInt_t) pobj)->val == 0;

#ifdef HAVE_FLOAT
	case OBJ_TYPE_FLT:
		/* The floats 0.0 and -0.0 are false */
		return (((pPmFloat_t) pobj)->val == 0.0)
		    || (((pPmFloat_t) pobj)->val == -0.0);
#endif				/* HAVE_FLOAT */

	case OBJ_TYPE_STR:
		/* An empty string is false */
		return ((pPmString_t) pobj)->length == 0;

	case OBJ_TYPE_TUP:
		/* An empty tuple is false */
		return ((pPmTuple_t) pobj)->length == 0;

	case OBJ_TYPE_LST:
		/* An empty list is false */
		return ((pPmList_t) pobj)->length == 0;

	case OBJ_TYPE_DIC:
		/* An empty dict is false */
		return ((pPmDict_t) pobj)->length == 0;

	case OBJ_TYPE_BOOL:
		/* C int zero means false */
		return ((pPmBoolean_t) pobj)->val == 0;

	default:
		/*
		 * The following types are always not false:
		 * CodeObj, Function, Module, Class, ClassInstance.
		 */
		return C_FALSE;
	}
}

/* Returns true if the item is in the container object */
PmReturn_t obj_isIn(pPmObj_t pobj, pPmObj_t pitem)
{
	PmReturn_t retval = PM_RET_NO;
	pPmObj_t ptestItem;
	int16_t i;
	uint8_t c;

	switch (OBJ_GET_TYPE(pobj)) {
	case OBJ_TYPE_TUP:
		/* Iterate over tuple to find item */
		for (i = 0; i < ((pPmTuple_t) pobj)->length; i++) {
			PM_RETURN_IF_ERROR(tuple_getItem(pobj, i, &ptestItem));

			if (obj_compare(pitem, ptestItem) == C_SAME) {
				retval = PM_RET_OK;
				break;
			}
		}
		break;

	case OBJ_TYPE_STR:
		/* Raise a TypeError if item is not a string */
		if ((OBJ_GET_TYPE(pitem) != OBJ_TYPE_STR)) {
			retval = PM_RET_EX_TYPE;
			break;
		}

		/* Empty string is alway present */
		if (((pPmString_t) pitem)->length == 0) {
			retval = PM_RET_OK;
			break;
		}

		/* Raise a ValueError if the string is more than 1 char */
		else if (((pPmString_t) pitem)->length != 1) {
			retval = PM_RET_EX_VAL;
			break;
		}

		/* Iterate over string to find char */
		c = ((pPmString_t) pitem)->val[0];
		for (i = 0; i < ((pPmString_t) pobj)->length; i++) {
			if (c == ((pPmString_t) pobj)->val[i]) {
				retval = PM_RET_OK;
				break;
			}
		}
		break;

	case OBJ_TYPE_LST:
		/* Iterate over list to find item */
		for (i = 0; i < ((pPmList_t) pobj)->length; i++) {
			PM_RETURN_IF_ERROR(list_getItem(pobj, i, &ptestItem));

			if (obj_compare(pitem, ptestItem) == C_SAME) {
				retval = PM_RET_OK;
				break;
			}
		}
		break;

	case OBJ_TYPE_DIC:
		/* Check if the item is one of the keys of the dict */
		retval = dict_getItem(pobj, pitem, &ptestItem);
		if (retval == PM_RET_EX_KEY) {
			retval = PM_RET_NO;
		}
		break;

	default:
		retval = PM_RET_EX_TYPE;
		break;
	}

	return retval;
}

int8_t obj_compare(pPmObj_t pobj1, pPmObj_t pobj2)
{
#ifdef HAVE_BYTEARRAY
	PmReturn_t retval;
	pPmObj_t pobj;
#endif				/* HAVE_BYTEARRAY */

	C_ASSERT(pobj1 != C_NULL);
	C_ASSERT(pobj2 != C_NULL);

	/* Check if pointers are same */
	if (pobj1 == pobj2) {
		return C_SAME;
	}

	/* If types are different, objs must differ */
	if (OBJ_GET_TYPE(pobj1) != OBJ_GET_TYPE(pobj2)) {
		return C_DIFFER;
	}
#ifdef HAVE_BYTEARRAY
	/* If object is an instance, get the thing it contains */
	if (OBJ_GET_TYPE(pobj1) == OBJ_TYPE_CLI) {
		retval =
		    dict_getItem((pPmObj_t) ((pPmInstance_t) pobj1)->cli_attrs,
				 PM_NONE, &pobj);
		PM_RETURN_IF_ERROR(retval);
		pobj1 = pobj;
	}
	if (OBJ_GET_TYPE(pobj2) == OBJ_TYPE_CLI) {
		retval =
		    dict_getItem((pPmObj_t) ((pPmInstance_t) pobj2)->cli_attrs,
				 PM_NONE, &pobj);
		PM_RETURN_IF_ERROR(retval);
		pobj2 = pobj;
	}

	/* If types are different, objs must differ */
	if (OBJ_GET_TYPE(pobj1) != OBJ_GET_TYPE(pobj2)) {
		return C_DIFFER;
	}
#endif				/* HAVE_BYTEARRAY */

	/* Otherwise handle types individually */
	switch (OBJ_GET_TYPE(pobj1)) {
	case OBJ_TYPE_NON:
		return C_SAME;

	case OBJ_TYPE_INT:
		return ((pPmInt_t) pobj1)->val ==
		    ((pPmInt_t) pobj2)->val ? C_SAME : C_DIFFER;

#ifdef HAVE_FLOAT
	case OBJ_TYPE_FLT:
		{
			pPmObj_t r_pobj;

			float_compare(pobj1, pobj2, &r_pobj, COMP_EQ);
			return (r_pobj == PM_TRUE) ? C_SAME : C_DIFFER;
		}
#endif				/* HAVE_FLOAT */

	case OBJ_TYPE_STR:
		return string_compare((pPmString_t) pobj1, (pPmString_t) pobj2);

	case OBJ_TYPE_TUP:
	case OBJ_TYPE_LST:
#ifdef HAVE_BYTEARRAY
	case OBJ_TYPE_BYA:
#endif				/* HAVE_BYTEARRAY */
		return seq_compare(pobj1, pobj2);

	case OBJ_TYPE_DIC:
		return dict_compare(pobj1, pobj2);

	default:
		break;
	}

	/* All other types would need same pointer to be true */
	return C_DIFFER;
}

#ifdef HAVE_PRINT
PmReturn_t obj_print(pPmObj_t pobj, uint8_t is_expr_repr, uint8_t is_nested)
{
	PmReturn_t retval = PM_RET_OK;

	C_ASSERT(pobj != C_NULL);

	/* Something gets printed unless it's None in an unnested expression */
	if (!
	    ((OBJ_GET_TYPE(pobj) == OBJ_TYPE_NON) && is_expr_repr
	     && !is_nested)) {
		gVmGlobal.somethingPrinted = C_TRUE;
	}

	switch (OBJ_GET_TYPE(pobj)) {
	case OBJ_TYPE_NON:
		if (!is_expr_repr || is_nested) {
			sli_puts((uint8_t *) "None");
		}
		break;
	case OBJ_TYPE_INT:
		retval = int_print(pobj);
		break;
#ifdef HAVE_FLOAT
	case OBJ_TYPE_FLT:
		retval = float_print(pobj);
		break;
#endif				/* HAVE_FLOAT */
	case OBJ_TYPE_STR:
		retval = string_print(pobj, (is_expr_repr || is_nested));
		break;
	case OBJ_TYPE_TUP:
		retval = tuple_print(pobj);
		break;
	case OBJ_TYPE_LST:
		retval = list_print(pobj);
		break;
	case OBJ_TYPE_DIC:
		retval = dict_print(pobj);
		break;
	case OBJ_TYPE_BOOL:
		sli_puts((((pPmBoolean_t) pobj)->val == C_TRUE)
			 ? (uint8_t *) "True" : (uint8_t *) "False");
		break;

	case OBJ_TYPE_CLI:
#ifdef HAVE_BYTEARRAY
		{
			pPmObj_t pobj2;

			retval =
			    dict_getItem((pPmObj_t) ((pPmInstance_t) pobj)->
					 cli_attrs, PM_NONE,
					 (pPmObj_t *) & pobj2);
			if ((retval == PM_RET_OK)
			    && (OBJ_GET_TYPE(pobj2) == OBJ_TYPE_BYA)) {
				retval = bytearray_print(pobj2);
				break;
			}
		}
#endif				/* HAVE_BYTEARRAY */

	case OBJ_TYPE_COB:
	case OBJ_TYPE_MOD:
	case OBJ_TYPE_CLO:
	case OBJ_TYPE_FXN:
	case OBJ_TYPE_CIM:
	case OBJ_TYPE_NIM:
	case OBJ_TYPE_NOB:
	case OBJ_TYPE_THR:
	case OBJ_TYPE_CIO:
	case OBJ_TYPE_MTH:
	case OBJ_TYPE_SQI:
		{
			uint8_t buf[17];
			sli_puts((uint8_t *) "<obj type 0x");
			sli_btoa16(OBJ_GET_TYPE(pobj), buf, sizeof(buf),
				   C_TRUE);
			sli_puts(buf);
			sli_puts((uint8_t *) " @ 0x");
			sli_ptoa16((intptr_t) pobj, buf, sizeof(buf), C_TRUE);
			sli_puts(buf);
			retval = plat_putByte('>');
			break;
		}

	default:
		/* Otherwise raise a TypeError */
		PM_RAISE(retval, PM_RET_EX_TYPE);
		break;
	}
	return retval;
}
#endif				/* HAVE_PRINT */

#ifdef HAVE_BACKTICK
PmReturn_t obj_repr(pPmObj_t pobj, pPmObj_t * r_pstr)
{
	uint8_t tBuffer[32];
	PmReturn_t retval = PM_RET_OK;
	uint8_t const *pcstr = (uint8_t *) tBuffer;;

	C_ASSERT(pobj != C_NULL);

	switch (OBJ_GET_TYPE(pobj)) {
	case OBJ_TYPE_INT:
		retval =
		    sli_ltoa10(((pPmInt_t) pobj)->val, tBuffer,
			       sizeof(tBuffer));
		PM_RETURN_IF_ERROR(retval);
		retval = string_new(&pcstr, r_pstr);
		break;

#ifdef HAVE_FLOAT
	case OBJ_TYPE_FLT:
		/* #212: Use homebrew float formatter */
		retval =
		    sli_ftoa(((pPmFloat_t) pobj)->val, tBuffer,
			     sizeof(tBuffer));
		sli_strlen((char *)tBuffer);
		retval = string_new(&pcstr, r_pstr);
		break;
#endif				/* HAVE_FLOAT */

	default:
		/* Otherwise raise a TypeError */
		PM_RAISE(retval, PM_RET_EX_TYPE);
		break;
	}

	return retval;
}
#endif				/* HAVE_BACKTICK */
